﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Fadd.Logging.Formatting;

namespace Fadd.Logging
{
	/// <summary>
	/// Default formatter.
	/// </summary>
	public class DefaultFormatter : IFormatter
	{
		private readonly DateTimeFormatter _dateTimeFormatter = new DateTimeFormatter();
		private string _formatString;
		private List<IPartFormatter> _lineFormatters = new List<IPartFormatter>();

		/// <summary>
		/// Initializes a new instance of the <see cref="DefaultFormatter"/> class.
		/// </summary>
		public DefaultFormatter()
		{
			Name = "Default";
			_dateTimeFormatter.FormatString = "yyyy-MM-dd hh:mm:ss.fff";

			_lineFormatters.Add(_dateTimeFormatter);
			_lineFormatters.Add(new TextFormatter(" "));
			_lineFormatters.Add(new ThreadIdFormatter());
			_lineFormatters.Add(new TextFormatter(" "));
			_lineFormatters.Add(new LogLevelFormatter());
			_lineFormatters.Add(new TextFormatter(" "));
			_lineFormatters.Add(new MessageFormatter());
			_lineFormatters.Add(new TextFormatter(" "));
			_lineFormatters.Add(new StackTraceFormatter());
			_lineFormatters.Add(new TextFormatter(" "));
		}

		#region IFormatter Members

		/// <summary>
		/// Load configuration.
		/// </summary>
		/// <param name="configuration">Configuration to load.</param>
		/// <exception cref="InvalidOperationException">If configuration has already been loaded.</exception>
		/// <exception cref="ArgumentNullException"><c>configuration</c> is null.</exception>
		public void LoadConfiguration(FormatterConfiguration configuration)
		{
			if (configuration == null)
				throw new ArgumentNullException("configuration");
			Name = configuration.Name;
			FormatString = configuration.Format;
		}


		/// <summary>
		/// Gets or sets formatter name.
		/// </summary>
		public string Name { get; private set; }

		/// <summary>
		/// Gets or sets log entry format string.
		/// </summary>
		public string FormatString
		{
			get { return _formatString; }
			set
			{
				if (!string.IsNullOrEmpty(value))
					_lineFormatters = ParseFormat(value);
				_formatString = value;
			}
		}

		/// <summary>
		/// Format a log entry
		/// </summary>
		/// <param name="entry">Entry to format</param>
		/// <returns>String with formatted entry.</returns>
		public string Format(LogEntry entry)
		{
            if (entry == null)
                return "NullEntry";

			string text = string.Empty;
			foreach (IPartFormatter formatter in _lineFormatters)
				text += formatter.Format(entry);
			if (entry.Exception != null)
				text += "\r\n" + entry.Exception;

			return text;
		}

		/// <summary>
		/// Format a date/time entry.
		/// </summary>
		/// <param name="dateTime">Date time to format</param>
		/// <returns>Standardized date format</returns>
		public string FormatDateTime(DateTime dateTime)
		{
			return _dateTimeFormatter.Format(dateTime);
		}

		/// <summary>
		/// Format a date entry.
		/// </summary>
		/// <param name="dateTime">Date time to format</param>
		/// <returns>string </returns>
		public string FormatDate(DateTime dateTime)
		{
			return dateTime.ToString("yyyy-MM-dd");
		}

		#endregion

		/// <summary>
		/// Parse a format string
		/// </summary>
		/// <param name="format"></param>
		/// <returns></returns>
		public static List<IPartFormatter> ParseFormat(string format)
		{
			int start = -1;
			var parts = new List<string>();
			for (int i = 0; i < format.Length; ++i)
			{
				switch (format[i])
				{
					case '{':
						if (start != -1)
							parts.Add(format.Substring(start, i - start));
						start = i;
						break;
					case '}':
						parts.Add(format.Substring(start, i - start));
						start = i + 1;
						break;
				}
			}


			// {Date:yyyy-MM-dd hh:mm:ss.ffff} {ThreadId} {StackTrace:3,40} {Message}
			var formatters = new List<IPartFormatter>();
			foreach (string part in parts)
			{
				if (part.StartsWith("{DateTime"))
				{
					if (part.Length > 9 && part[9] == ':')
						formatters.Add(new DateTimeFormatter { FormatString = part.Substring(10) });
					else
						formatters.Add(new DateTimeFormatter());
				}
				else if (part.StartsWith("{ThreadId"))
					formatters.Add(new ThreadIdFormatter());
				else if (part.StartsWith("{StackTrace"))
				{
					if (!part.StartsWith("{StackTrace:"))
					{
						formatters.Add(new StackTraceFormatter());
						continue;
					}

					string[] subparts = part.Substring(12).Split(',');
					var formatter = new StackTraceFormatter();
					if (subparts.Length > 0)
						formatter.Count = int.Parse(subparts[0]);
					if (subparts.Length > 1)
						formatter.PadLength = int.Parse(subparts[1]);
					if (subparts.Length > 2)
						formatter.SkipCount = int.Parse(subparts[2]);
					formatters.Add(formatter);
				}
				else if (part.StartsWith("{Message"))
					formatters.Add(new MessageFormatter());
				else if (part.StartsWith("{LogLevel"))
					formatters.Add(new LogLevelFormatter());
				else
					formatters.Add(new TextFormatter {Text = part});
			}
			return formatters;
		}
	}
}